\section{Tareas}
\label{sec:tareas}

\subsection{Consideraciones generales}
En este punto tenemos todas las herramientas de nuestro sistema operativo preparadas para empezar a lidiar con las tareas. El objetivo es implementar \textit{multitasking}, para ello vamos a asignar un \textit{slot} de tiempo para cada tarea y utilizaremos el mecanismo de \textit{task switching} que el procesador nos ofrece para intercambiarlas a gran velocidad. El mismo consiste en mantener contextos (TSS) que contengan el estado de la tarea que se esta ejecutando para que el procesador pueda realizar el intercambio de tarea en forma autom'atica.

Como nuestro kernel es muy simple va a trabajar con las tareas fijas, embebidas y con un m'aximo de cantidad de tareas previamente establecido. Especificamente trabajaremos como m'aximo con 10 tareas (\texttt{tarea worm}) m'as una tarea auxiliar (\texttt{tarea idle}). Esta 'ultima se ejecutar'a cuando no hay otras tareas a correr. 

\subsection{TSSs y descriptores}

Siguiendo estas pautas y utilizando el c'odigo que de la c'atedra, primero cargamos en la GDT los descriptores de las TSS que vamos a utilizar (funci'on \texttt{inicializar\_tss\_desc}). En principio necesitamos once en total: diez para las \texttt{tareas worm} y uno extra para la \texttt{tarea idle}. Sin embargo tambi'en necesitamos uno extra para la tarea inicial, que es la que estoy corriendo actualmente (antes de cualquier cambio de tarea). Esto se debe a que el procesador guarda autom'aticamente el contexto antes de realizar el cambio, por lo que debemos proveer un lugar donde salvar dicha informaci'on. Para cargar cada descriptor creamos la funci'on \texttt{cargar\_tss\_desc}, la misma toma como par'ametros la posici'on de la GDT donde lo quiero crear y la direcci'on de memoria donde se encuentra la TSS. En la tabla \ref{tab:gdt2} podemos ver como queda nuestra GDT despu'es de estas operaciones.

\begin{table}[H]
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
\sc{'indice} & \sc{ selector} & \sc{ segmento}\\ \hline
0  & 0x0  & NULL\\ \hline
2  & 0x10 & Video\\ \hline
3  & 0x18 & Tarea Inicial \\ \hline
4  & 0x20 & Datos\\ \hline
5  & 0x28 & Tarea Idle\\ \hline
6  & 0x30& C'odigo\\ \hline
7  & 0x38& Tarea Worm\\ \hline
...  & ... & ... \\ \hline
8  & 0x80& Tarea Worm\\ \hline
\end{tabular}
\caption{Entradas en la tabla GDT}
\label{tab:gdt2}
\end{center}
\end{table}

Por ultimo, cargarmos el contexto de la \texttt{tarea idle} pidiendo una pagina de usuario para la pila. Esto lo realizamos con la funci'on \texttt{cargar\_tss} que toma como par'ametro la direcci'on de la TSS a cargar, el mapa de paginaci'on de la tarea(cr3 a cargar), la direcci'on de la pila (para cargar EBP y ESP) y la direcci'on donde se encuentra el c'odigo ejecutable(para cargar el EIP). Estas ultimas dos direcciones(pila y eip) son virtuales, es decir, son direcciones que tienen sentido dentro del mapa de paginaci'on de la tarea. En cambio la direcci'on del mapa de memoria es f'isica, pues es la que utiliza el procesador para cargar el cr3 en el cambio de tarea automatico. 

A su vez las TSSs siempre las cargamos con el flag de interrupciones (IF) activo, es decir que cuando se produzca el cambio de tarea las interrupciones estar'an habilitadas. Es por esto que nunca tuvimos que habilitarlas en la tarea inicial. Tambi'en es importante destacar que esta funci'on recibe la direcci'on de inicio de la pila, por lo que cuando establece el ESP y EBP tiene que sumarle 4k para que apunte al fondo de la pagina.

Si bien necesitamos una TSS para la tarea incial, no necesitamos llenarla de datos v'alidos, pues el kernel ya se encuentra en esa tarea y al realizar el primer \texttt{task switchitng} se van a almacenar los datos correctamente, por lo que podriamos eventualmente volver a esta tarea con otro cambio. Sin embargo esto no sucede porque nunca tenemos la necesidad de cambiar a la tarea inicial nuevamente.

\subsubsection{Primera tarea}

Una vez que tenemos los descriptores y la primer TSS cargados queremos realizar un cambio a la tarea idle. Para ello, primero cargamos desde el kernel el registro TR con el selector de la tarea inicial, de esta manera el procesador sabe que esa es la tarea que estamos corriendo. Luego hacemos un \texttt{jmp far} al selector de la GDT que contiene la tarea idle(\texttt{0x28}). En ese momento el procesador guarda el contexto de la tarea actual en la TSS que creamos para tal fin, trae el contexto de la nueva tarea y comienza su ejecuci'on\footnote{Destacar que en este momento la nueva tarea esta corriendo con las interrupciones habilitadas}.

\subsection{Preguntas}

\paragraph*{Pregunta 11:} Colocando un breakpoint luego de la cargar una tarea, ?`c'omo se puede verificar, utilizando el debugger de Bochs, que la tarea se carg'o correctamente? C'omo llega a esta conclusi'on?\\
\textbf{Respuesta:} Para constatar que la tarea inicial fue cargada correctamente, podemos colocar un breakpoint antes y despu'es de la instruccion que carga la TR (ltr). En ambos momentos con el comando \texttt{info tss} del debugger de bochs podemos observar el TR cargado, si es valido (igual a uno) y los valores de la TSS apuntados por ese descriptor. En nuestro caso, despu'es de cargar el registro observamos \texttt{tr: 0x18 valid=1} que se corresponde con la entrada en GDT de nuestra tarea inicial. 

\paragraph*{Pregunta 12:} ?`C'omo puede hacer para verificar si la conmutaci'on de tarea fue exitosa?\\
\textbf{Respuesta:} En nuestro caso el primer cambio de tarea era hacia la tarea idle, que dibujaba un reloj en pantalla, por lo que la observaci'on fue directa. Sin embargo hay otros factores que se puden apreciar. Inicialmente no se deber'ian producir excepciones despu'es del \texttt{jmp far}. Luego, se puede interrumpir la ejecuci'on del bochs (ctrl + c) y con el comando \texttt{info gdt} examinar la tss de la tarea en ejecuci'on. Deberiamos observar que la tarea se encuentra ocupada(bit busy encendido).

\paragraph*{Pregunta 13:} Se sabe que las tareas llaman a la interrupciones 88 y 99 y por eso debe realizarse el ejercicio anterior antes de conmutar de tarea. ?`Qu'e ocurre si no se hace? ?`Por qu'e?\\
\textbf{Respuesta:} Si no estan definidos las rutina de atencion de una interrupci'on (por ejemplo no estan cargadas en la IDT), el procesador realiza una excepcion (\#GP). Una manera muy f'acil de constatarlo es haciendo por ejemplo int 100 en el c'odigo del kernel. Es por eso que primero debiamos definir las rutinas de interrupci'on antes de lanzar las tareas que eventualmente podrian llamar a esas rutinas.

\subsection{Scheduler}

Una vez que logramos lanzar la tarea idle correctamente, queremos que cada vez que se produce la interrupci'on de reloj podamos realizar un cambio de tarea. De esta manera vamos a podes emular el \textit{multitasking}. Para esto modificamos la rutina de atencion de la interrupci'on 32 (reloj) para que ademas de actualizar el reloj de la pantalla pregunte cual es la pr'oxima tarea a ejecutarse y salte a ella. 

Para saber cual es la pr'oxima tarea, en el scheduler(archivo sched.c) creamos la funci'on \texttt{proxima\_tarea} que devuelve el selector de la GDT que contiene la TSS de la pr'oxima tarea a ejecutar. Si no encuentra una pr'oxima tarea, entonces devuelve cero y el handler de interrupci'on puede detectarlo para no realizar un cambio de tarea. 

En una primera etapa, hicimos una implementaci'on b'asica de esta funci'on, la misma devolv'ia la primera tarea worm si estabamos corriendo la tarea idle y sino devolv'ia cero. En una siguiente etapa la modificamos para que lance intercaladamente la primera tarea worm y la tarea idle, pudiendo observar en pantalla como el gusano se desarrollaba sin que se interrumpa la animacion del reloj idle. 

Para la implementaci'on final de esta funci'on definimos un arreglo de selectores de GDT de diez posiciones. Basicamente cada 'indice simboliza el \texttt{pid} mientras que cada entrada puede contener cero (ese proceso no se esta ejecutando) o el selector de la TSS correspondiente a ese pid. Para hacerlo mas ordenandamente, el primer proceso esta asociado a la primera entrada para la tarea worm en la GDT (posici'on 7, selector 0x38) y asi sucesivamente. Tambien definimos la variable \texttt{tarea\_actual} que va a mantener el 'indice de la tarea que se esta ejecutando en ese momento o -1 si se esta ejecutando la tarea osciosa. 

Una vez que tenemos estas estructuras la funci'on se reduce a recorrer el arreglo a partir del 'indice determinado por la \texttt{tarea\_actual} (o a partir de cero si se estaba ejecutando la tarea idle) hasta encontrar algun valor distinto de cero. Si se encuentra, entonces se devuelve el valor almacenado en el arreglo (el selector de la GDT), sino se devuelve 0, como ya habiamos mencionado. De esta manera, con solo llenar una posici'on de este arreglo alcanza para lanzar una tarea.

Ahora bien, todo esto es v'alido siempre y cuando tengamos el contexto de la primera tarea worm cargado previamente. Es por eso que hicimos una funci'on \texttt{inicializar\_scheduler} que se encarga de inicializar las estructuras de datos antes mencionadas y cargar la TSS de la primera tarea worm. Esta 'ultima tarea se la delegamos a la funci'on \texttt{lanzar\_gusano}. Esta tarea que suena tan sencilla tiene varias partes es por eso que tambi'en la modularizamos. Basicamente hace las siguientes operaciones:

\begin{itemize}
\item[a)] busca el pr'oximo lugar libre en el arreglo de tareas a ejecutarse (funci'on \texttt{slot\_libre})
\item[b)] crea el mapa de memoria que va necesitar la tarea, con sus respectivos c'odigo, datos y pila (funci'on \texttt{crear\_mapeo\_tarea})
\item[c)] carga la TSS con los datos v'alidos que permitan iniciar la ejecuci'on de la tarea (funci'on \texttt{cargar\_tss})
\item[d)] agrega la tarea a la lista de tareas a ejecutarse (funci'on \texttt{agregar\_proceso})
\end{itemize}

La funci'on \texttt{slot\_libre} recorre desde el principio el arreglo de tareas buscando un espacio, si no lo hay, devuelve -1 y la funci'on \texttt{lanzar\_gusano} termina. La funci'on \texttt{agregar\_proceso} es muy simple, sencillamente calcula el selector correspondiente para el pid pasado como par'ametro y lo agrega al arreglo de tareas. 

La funci'on \texttt{cargar\_tss} ya la teniamos, pues la creamos para cargar la tarea idle, y la hicimos param'etrica para que nos sirva en este caso tambi'en. Aqui le pasamos como cr3 el resultado de la funci'on del item b), la direcci'on de la pila hace referencia al mapeo de la tarea, por lo que esta establecida en 0x30000 como pide el enunciado. Por 'ultimo el eip se establece en cero, pues es ahi donde esta la pagina del c'odigo de la tarea.

Claramente la funci'on mas complicada en esta etapa es \texttt{crear\_mapeo\_tarea}. La misma toma como par'ametros la direcci'on f'isica de la pagina del c'odigo, datos y pila de la tarea. Basicamente realiza las siguientes operaciones:

\begin{itemize}
\item[a)] crea un mapa de memoria con \texttt{identity map} en los 2Mb mas bajos \\
(funci'on \texttt{inicializar\_dir\_usuario})
\item[b)] pide tres paginas de usuario para el c'odigo, datos y pila de la tarea a ejecutar(funci'on \texttt{pagina\_libre\_usuario})
\item[c)] copia las paginas que contienen el c'odigo y los datos de la tarea a las paginas recientemente pedidas(funci'on \texttt{copiar\_pagina})
\item[c$_{1}$)] si la direcci'on de la pila pasada por par'ametro no es cero entonces tambi'en copia la pagina de pila(funci'on \texttt{copiar\_pagina})
\item[d)] mapea las paginas pedidas en el mapa anteriormente creado(funci'on \texttt{mapear\_pagina})
\item[e)] devuelve el mapa generado
\end{itemize}

La 'unica funci'on que explicamos todav'ia es \texttt{copiar\_pagina}. La misma realiza una copia bit a bit (tecnicamente lo hace de a 32 bits) desde la p'agina fuente a la p'agina destino. Es importante destacar que tal vez las p'aginas solicitadas a copiarse no esten disponibles en nuestro mapa de memoria. Como dicha funci'on pertence al kernel y s'olo 'el la va a invocar, podemos utilizar las funciones de mapeo y desmapeo de memoria para acceder temporalmente a dichas p'aginas. Esta rutina es lo suficientemente inteligente para no pisar un mapeo anteriormente definido con el mapeo temporal. Notemos adem'as que las p'aginas no pueden ser accedidas cuando por ejemplo son p'aginas libres de usuario, pues las mismas se encuentran por encima de los 2Mb de identity maping que tiene definido el kernel. Por el contrario las paginas libres de kernel s'i son direccionables desde el mapa que estamos usando.

De esta manera, modularizando y reutilizando c'odigo pudimos lanzar el primer gusano. En este punto pensamos que ser'ia interesante, con el objetivo de testear mas r'apidamente nuestro c'odigo, lanzar mas gusanos desde la inicializacion del scheduler. Efectivamente lo hicimos y funcion'o adecuadamente, sin embargo debido a las caracter'isticas de la tarea worm no pudimos ver ninguna diferencia en pantalla, pues los gusanos escribian en las mismas posiciones de pantalla. Es por eso que hicimos un peque'no \texttt{hack} para la tarea worm. El mismo esta explicado en el Ap'endice \ref{sec:hack}.

\subsection{Llamadas al sistema operativo}
\subsubsection{Tick y pid: interrupciones 99 y 88}
La modificaci'on de las rutinas de atencion de estas interrupciones son muy simples. La n'umero 88 devuelve en eax el pid del proceso que se esta ejecutando. Ese dato es f'acil de encontrar pues nuestro scheduler tiene una variable global que guarda el 'indice de la tarea activa, que es exactamente el pid requerido. Para la interrupci'on 99 es necesario definir una variable gloabal (\texttt{tick}) inicializada en cero. Por su parte la rutina de atenci'on de reloj se encargara de incrementarla. Una vez realizados estos cambios todo se reduce en devolver el valor por eax como lo hicimos previamente.

\subsubsection{Eliminando procesos: interrupci'on 77 y teclado}
Esta rutina tambi'en es simple, pues se reduce a llamar a una funci'on en C (\texttt{eliminar\_proceso}) que lo 'unico que hace es poner en cero el arreglo de tareas en la posici'on especificada por el pid. Lo mismo sucede con la atencion del teclado. La modificamos para que c'alcule que n'umero se est'a tocando y se lo pase como par'ametro a dicha funci'on. Es importante destacar que la actualizacion de los pids en la pantalla (esquina superior derecha) se realiza en las funciones \texttt{agregar\_proceso} y \texttt{eliminar\_proceso}.

\subsubsection{Duplicando procesos: interrupcion 66}

Claramente esta es la tarea mas compleja de todo este trabajo pr'actico. Para duplicar un proceso no solo debemos generar un nuevo espacio en memoria para que corra, sino que tambi'en tenemos que copiar el c'odigo, datos y pila del proceso original y el estado de todos los registros. Hasta ahora veniamos usando Interrupt Gates para las interrupciones, por lo que la rutina de atenci'on corre usando el contexto de la tarea actual. Esto tiene sus ventajas y complicaciones. El lado bueno es que al compartir el mismo mapa de memoria es f'acil acceder al estado actual del c'odigo, datos y pila. La contraparte es que tenemos que tener mucho cuidado de no destruir la informacion de los registros desde la rutina, ademas de que la pila del proceso en ejecuci'on se va a ``ensuciar'' con los datos que el procesador apila para la atencion de la interrupci'on.

Otra opci'on es utilizar una Task Gate para el manejo de esta interrupci'on. La principal ventaja es que cuando se invoca la interrupci'on se produce un cambio de tarea, por lo que el contexto de la tarea que estaba en ejecuci'on se salva. Por ende la duplicacion se reduce a copiar la TSS m'as las p'aginas de c'odigo, datos y pila. Ademas la pila del proceso estar'ia limpia, pues los datos de la interrupci'on se pasaran por la pila de la tarea de atenci'on. La 'unica desventaja de este m'etodo es que, como no compartimos el mapa de memoria, tenemos que encontrar de alguna manera las paginas de c'odigo, pila y datos.

Valorando las dos posibilidades creemos que la 'ultima es la mas conveniente porque, a pesar de agregar un \textit{overhead} en el tiempo de ejecuci'on, nos permite realizar una implementaci'on mas limpia, con menos posibilidad de cometer errores(contando par'ametros en la pila, etc).

Para realizar esta implementaci'on, modificamos la entrada 66 en la IDT para que contenga una Task Gate. Esto lo realizamos duplicando y modificando la macro \texttt{IDT\_ENTRY} provista por la c'atedra(archivo idt.c) y luego invocandola desde la rutina de inicializacion de la IDT. Es importante destacar que para armar esta entrada tuvimos que definir una nueva TSS con su correspondiente entrada en la GDT, la misma albergara el contexto de la rutina de interrupci'on. Para esto creamos la variable \texttt{tarea\_fork} (archivo tss.c) y utilizamos la segunda entrada en la GDT que estaba libre y parece que nos estaba esperando. Ademas, la Task Gate la cargamos para que siga apuntando a la funci'on \texttt{\_isr66}.

El contexto de esta rutina la inicializamos en la misma que carga las entradas en la GDT y carga la TSS de la tarea idle (\texttt{inicializar\_tss\_desc}). Los valores utilizados fueron los mismos que para la tarea idle, solo que para la pila pedimos una pagina libre del kernel (y no del usuario) pues es c'odigo de kernel el que estamos ejecutando. Ademas, el valor inicial de los EFLAGS es distinto, pues aqui es donde tenemos que deshabilitar las interrupciones para que la rutina de atencion se comporte de la misma manera que una Interrupt Gate. 

Con estas modificaciones, al producirse la interrupci'on seguimos entrando en la funci'on \texttt{\_isr66} pero con un contexto limpio, y a'un mas importante, con el contexto de la tarea padre completamente salvado y congelado en la TSS. El pr'oximo paso fue modificar esta rutina para que funcione correctamente despu'es de estos cambios. Basicamente todo se redujo a agregar un \texttt{jmp \_isr66} despu'es de la instruccion \texttt{iret}. Esto se debe a que una vez que la rutina es ejecutada por primera vez, el \texttt{iret} se produce un cambio de tarea a la tarea padre y el contexto de la rutina se salva. Es decir que el EIP se guardara apuntando a esa posici'on, por lo que cuando se vuelve a ejecutar esta rutina hay que hacerla saltar al inicio del c'odigo. 

Ahora nuestra rutina esta lista para llamar a la funci'on en C \texttt{duplicar\_proceso}. La misma, no respeta la aridad propuesta por la c'atedra pues no toma ning'un argumento. Esto se debe a que para determinar cual es la tarea que se quiere duplicar utilizamos el campo Previous Task Link (PTL) almacenado en la TSS de nuestro handler. El procesador actualiza autom'aticamente este campo con el selector del la GDT que contiene el descriptor de TSS que fue interrumpida. Cabe destacar que como tenemos una relacion uno a uno ordenanda entre el arreglo de tareas del scheduler, el arreglo de TSS y las entradas en la GDT, es simple llegar a la TSS de la tarea padre.

Entonces la \texttt{duplicar\_proceso} realiza las siguientes operaciones:

\begin{itemize}
\item[a)] pide un pid libre, si no hay entonces termina (funci'on \texttt{slot\_libre})
\item[b)] accede a la TSS padre a partir del campo PTL de la TSS de la rutina de interrupci'on
\item[c)] busca las direcciones fisicas de las paginas de c'odigo, datos y pila en el mapa de memoria de la tarea padre (funci'on \texttt{buscar\_entrada})
\item[d)] crea un mapa de memoria copiando las paginas calculadas en el punto anterior (funci'on \texttt{crear\_mapeo\_tarea})
\item[e)] duplica la TSS del padre, modificando el valor del cr3 para que use el mapeo del punto anterior (funci'on \texttt{duplicar\_tss})
\item[f)] modifica el valor de EAX en la TSS del padre y del nuevo proceso como lo pide el enunciado
\item[g)] agrega el nuevo proceso en el scheduler (funci'on \texttt{agregar\_proceso})
\end{itemize}

Como podemos ver, reutilizamos todo el c'odigo que pudimos. Notemos que en el punto c) podemos buscar las direcciones facilmente porque sabemos en que posiciones virtuales se encuentran (0x0 c'odigo, 0x20000 para datos, 0x30000 para pila) y por lo tanto podemos posteriormente copiarlas a las paginas del nuevo proceso. Por otra parte la funci'on \texttt{duplicar\_tss} realiza una copia bit a bit\footnote{Tecnicamente hace una copia de a 32 bits por cuestiones de eficiencia} de toda la TSS padre a la TSS apuntada por el resultado de la funci'on \texttt{slot\_libre} en el arreglo de TSSs. Una vez terminada la copia sobreescribe el valor del cr3 con el valor recibido como par'ametro.


\subsection{Tarea mondrian}

Para ejecutar esta nueva tarea creamos una TSS (variable \texttt{tarea\_modrian}). Ademas cargamos su descriptor en la GDT despu'es de todas las tareas worm (posici'on 0x88) usando la misma funci'on que utilizamos para las otras tareas. Lo mismo hicimos para cargar el contenido inicial de la TSS, solo que modificando los par'ametros para que copie el c'odigo y datos de la nueva tarea. Todas estas operaciones las agrupamos en la funci'on \texttt{preparar\_mondrian}. Una vez que tuvimos todo esto listo procedimos a invocarla desde la inicializaci'on del scheduler, en lugar de nuestra funci'on \texttt{lanzar\_gusano}. 

\subsubsection{Paginas no mapeadas}
Como era previsible, al ejecutarse la nueva tarea se produjo una excepci'on Page Fault (\#PF) que fue correctamente atrapada por nuestro handler, mostrando en pantalla los registros del procesador. Cuando se produce este tipo de excepci'on el procesador deja en el registro CR2 la posici'on de memoria a la que se trato de acceder y, por ende, que produjo esta interrupci'on. Por lo que modificamos nuestra funci'on \texttt{preparar\_mondrian} para que pida una pagina libre de usuario y la agrege al mapa de memoria de la tarea. Realizando esta operaci'on tres veces pudimos observar que las posiciones accedidas por la tarea eran: 0x10101010, 0x20202020 y 0x30303030. Una vez resuelto este problema pudimos ver un cita famosa de una gran pel'icula del a'no 1983. Si bien s'olo uno de los participantes del grupo la habia visto (justamente el 'unico que nacio antes de que se filmara) entendimos la profundidad(?) del mensaje. La parte faltante de la cita, que responde al item b) del ejecicio 8 se puede obtener a traves de la ejecuci'on de nuestro c'odigo, aniquilando todos los gusanos, esperando que el mensaje termine y presionando cualquier tecla\protect\footnote{Para la diversi'on del corrector incluimos en nuestro c'odigo un huevo de pascua (eastern egg), el mismo esta embebido en el handler del teclado. Como se dispara? Que hace?}.



